#=====================================================================
# LedgerSMB
# Small Medium Business Accounting software
# http://www.ledgersmb.org/
#
# Copyright (C) 2006
# This work contains copyrighted information from a number of sources all used
# with permission.
#
# This file contains source code included with or based on SQL-Ledger which
# is Copyright Dieter Simader and DWS Systems Inc. 2000-2005 and licensed
# under the GNU General Public License version 2 or, at your option, any later
# version.  For a full list including contact information of contributors,
# maintainers, and copyright holders, see the CONTRIBUTORS file.
#
# Original Copyright Notice from SQL-Ledger 2.6.17 (before the fork):
# Copyright (C) 2003
#
#  Author: DWS Systems Inc.
#     Web: http://www.sql-ledger.org
#
#  Contributors:
#
#
#======================================================================
#
# This file has undergone whitespace cleanup.
#
#======================================================================
#
# Check and receipt printing payment module backend routines
# Number to text conversion routines are in
# locale/{countrycode}/Num2text
#
#======================================================================

package CP;
use LedgerSMB::Sysconfig;

sub new {

    my ( $type, $countrycode ) = @_;

    $self = {};

    use LedgerSMB::Num2text;
    use LedgerSMB::Locale;
    $self->{'locale'} = LedgerSMB::Locale->get_handle($countrycode);

    bless $self, $type;

}

sub paymentaccounts {

    my ( $self, $myconfig, $form ) = @_;

    my $dbh = $form->{dbh};

    my $query = qq|SELECT accno, description, link
					 FROM chart
					WHERE link LIKE ?
				 ORDER BY accno|;

    my $sth = $dbh->prepare($query);
    $sth->execute("%$form->{ARAP}%") || $form->dberror($query);

    $form->{PR}{ $form->{ARAP} } = ();
    $form->{PR}{"$form->{ARAP}_paid"} = ();

    while ( my $ref = $sth->fetchrow_hashref(NAME_lc) ) {

        foreach my $item ( split /:/, $ref->{link} ) {

            if ( $item eq $form->{ARAP} ) {
                push @{ $form->{PR}{ $form->{ARAP} } }, $ref;
            }

            if ( $item eq "$form->{ARAP}_paid" ) {
                push @{ $form->{PR}{"$form->{ARAP}_paid"} }, $ref;
            }
        }
    }

    $sth->finish;

    # get currencies and closedto
    $query = qq|
		SELECT value, (SELECT value FROM defaults
		                WHERE setting_key = 'closedto'), 
		       current_date
		  FROM defaults
		 WHERE setting_key = 'curr'|;

    ( $form->{currencies}, $form->{closedto}, $form->{datepaid} ) =
      $dbh->selectrow_array($query);

    if ( $form->{payment} eq 'payments' ) {

        # get language codes
        $query = qq|SELECT *
					  FROM language
				  ORDER BY 2|;

        $sth = $dbh->prepare($query);
        $sth->execute || $self->dberror($query);

        $form->{all_language} = ();

        while ( $ref = $sth->fetchrow_hashref(NAME_lc) ) {
            push @{ $form->{all_language} }, $ref;
        }

        $sth->finish;

        $form->all_departments( $myconfig, $dbh, $form->{vc} );
    }

    $dbh->commit;

}

sub get_openvc {

    my ( $self, $myconfig, $form ) = @_;

    my $dbh = $form->{dbh};

    my $arap = ( $form->{vc} eq 'customer' ) ? 'ar' : 'ap';
    my $query = qq|SELECT count(*)
					 FROM $form->{vc} ct, $arap a
					WHERE a.$form->{vc}_id = ct.id
					  AND a.amount != a.paid|;

    my ($count) = $dbh->selectrow_array($query);

    my $sth;
    my $ref;
    my $i = 0;

    my $where = qq|WHERE a.$form->{vc}_id = ct.id
					 AND a.amount != a.paid|;

    if ( $form->{ $form->{vc} } ) {
        my $var = $dbh->quote( $form->like( lc $form->{ $form->{vc} } ) );
        $where .= " AND lower(name) LIKE $var";
    }

    # build selection list
    $query = qq|SELECT DISTINCT ct.*
				  FROM $form->{vc} ct, $arap a
				$where
			  ORDER BY name|;

    $sth = $dbh->prepare($query);
    $sth->execute || $form->dberror($query);

    while ( $ref = $sth->fetchrow_hashref(NAME_lc) ) {
        $i++;
        push @{ $form->{name_list} }, $ref;
    }

    $sth->finish;

    $form->all_departments( $myconfig, $dbh, $form->{vc} );

    # get language codes
    $query = qq|SELECT *
				  FROM language
			  ORDER BY 2|;

    $sth = $dbh->prepare($query);
    $sth->execute || $self->dberror($query);

    $form->{all_language} = ();

    while ( $ref = $sth->fetchrow_hashref(NAME_lc) ) {
        push @{ $form->{all_language} }, $ref;
    }

    $sth->finish;

    # get currency for first name
    if ( @{ $form->{name_list} } ) {

        # Chris T:  I don't like this but it seems safe injection-wise
        # Leaving it so we can change it when we go to a new system
        $query = qq|SELECT curr 
					  FROM $form->{vc}
					 WHERE id = $form->{name_list}->[0]->{id}|;

        ( $form->{currency} ) = $dbh->selectrow_array($query);
        $form->{currency} ||= $form->{defaultcurrency};
    }

    $dbh->commit;

    $i;
}

sub get_openinvoices {

    my ( $self, $myconfig, $form ) = @_;

    my $null;
    my $department_id;

    # connect to database
    my $dbh = $form->{dbh};

    $vc_id = $dbh->quote( $form->{"$form->{vc}_id"} );
    my $where = qq|WHERE a.$form->{vc}_id = $vc_id
					 AND a.amount != a.paid|;

    $curr = $dbh->quote( $form->{currency} );
    $where .= qq| AND a.curr = $curr| if $form->{currency};

    my $sortorder = "transdate, invnumber";

    my ($buysell);

    if ( $form->{vc} eq 'customer' ) {
        $buysell = "buy";
    }
    else {
        $buysell = "sell";
    }

    if ( $form->{payment} eq 'payments' ) {

        $where = qq|WHERE a.amount != a.paid|;
        $where .= qq| AND a.curr = $curr| if $form->{currency};

        if ( $form->{duedatefrom} ) {
            $where .= qq| AND a.duedate >= 
				| . $dbh->quote( $form->{duedatefrom} );
        }

        if ( $form->{duedateto} ) {
            $where .=
              qq| AND a.duedate <= | . $dbh->quote( $form->{duedateto} );
        }

        $sortorder = "name, transdate";
    }

    ( $null, $department_id ) = split /--/, $form->{department};

    if ($department_id) {
        $where .= qq| AND a.department_id = $department_id|;
    }

    my $query = qq|SELECT a.id, a.invnumber, a.transdate, a.amount, a.paid,
						  a.curr, c.name, a.$form->{vc}_id, c.language_code
					 FROM $form->{arap} a
					 JOIN $form->{vc} c ON (c.id = a.$form->{vc}_id)
				   $where
				 ORDER BY $sortorder|;

    my $sth = $dbh->prepare($query);
    $sth->execute || $form->dberror($query);

    $query = qq|SELECT s.spoolfile
				  FROM status s
				 WHERE s.formname = '$form->{formname}'
				   AND s.trans_id = ?|;

    my $vth = $dbh->prepare($query);

    my $spoolfile;

    while ( $ref = $sth->fetchrow_hashref(NAME_lc) ) {
        $form->db_parse_numeric(sth=>$sth, hashref=>$ref);
        # if this is a foreign currency transaction get exchangerate
        $ref->{exchangerate} =
          $form->get_exchangerate( $dbh, $ref->{curr}, $ref->{transdate},
            $buysell )
          if ( $form->{currency} ne $form->{defaultcurrency} );

        $vth->execute( $ref->{id} );
        $ref->{queue} = "";

        while ( ($spoolfile) = $vth->fetchrow_array ) {
            $ref->{queued} .= "$form->{formname} $spoolfile ";
        }

        $vth->finish;
        $ref->{queued} =~ s/ +$//g;

        push @{ $form->{PR} }, $ref;
    }

    $sth->finish;
    $dbh->commit;

}

sub post_payment {

    my ( $self, $myconfig, $form ) = @_;

    # connect to database, turn AutoCommit off
    my $dbh = $form->{dbh};

    my $sth;

    my ($paymentaccno) = split /--/, $form->{account};

    # if currency ne defaultcurrency update exchangerate
    if ( $form->{currency} ne $form->{defaultcurrency} ) {

        $form->{exchangerate} =
          $form->parse_amount( $myconfig, $form->{exchangerate} );

        if ( $form->{vc} eq 'customer' ) {
            $form->update_exchangerate( $dbh, $form->{currency},
                $form->{datepaid}, $form->{exchangerate}, 0 );
        }
        else {
            $form->update_exchangerate( $dbh, $form->{currency},
                $form->{datepaid}, 0, $form->{exchangerate} );
        }

    }
    else {
        $form->{exchangerate} = 1;
    }

    my $query = qq|
		SELECT (SELECT value FROM defaults 
		         WHERE setting_key='fxgain_accno_id'), 
		       (SELECT value FROM defaults
		         WHERE setting_key='fxloss_accno_id')|;

    my ( $fxgain_accno_id, $fxloss_accno_id ) = $dbh->selectrow_array($query);

    my ($buysell);

    if ( $form->{vc} eq 'customer' ) {
        $buysell = "buy";
    }
    else {
        $buysell = "sell";
    }

    my $ml;
    my $where;

    if ( $form->{ARAP} eq 'AR' ) {

        $ml    = 1;
        $where = qq| (c.link = 'AR' OR c.link LIKE 'AR:%') |;

    }
    else {

        $ml = -1;
        $where =
          qq| (c.link = 'AP' OR c.link LIKE '%:AP' OR c.link LIKE '%:AP:%') |;

    }

    my $paymentamount = $form->parse_amount( $myconfig, $form->{amount} );

    # query to retrieve paid amount
    $query = qq|SELECT paid 
				  FROM $form->{arap}
				 WHERE id = ?
			FOR UPDATE|;

    my $pth = $dbh->prepare($query) || $form->dberror($query);

    my %audittrail;

    # go through line by line
    for my $i ( 1 .. $form->{rowcount} ) {

        $form->{"paid_$i"} =
          $form->parse_amount( $myconfig, $form->{"paid_$i"} );
        $form->{"due_$i"} = $form->parse_amount( $myconfig, $form->{"due_$i"} );

        if ( $form->{"checked_$i"} && $form->{"paid_$i"} ) {

            $paymentamount -= $form->{"paid_$i"};

            # get exchangerate for original
            $query = qq|
				SELECT $buysell
				  FROM exchangerate e
				  JOIN $form->{arap} a 
				       ON (a.transdate = e.transdate)
				 WHERE e.curr = ?
				       AND a.id = ?|;

            my $sth = $dbh->prepare($query);
            $sth->execute( $form->{currency}, $form->{"id_$i"} );
            my @exchange = $sth->fetchrow_array();
            $form->db_parse_numeric(sth=>$sth, arrayref=>\@exchange);
            my $exchangerate = shift @exchange;

            $exchangerate = 1 unless $exchangerate;

            $query = qq|
				SELECT c.id
				  FROM chart c
				  JOIN acc_trans a ON (a.chart_id = c.id)
				 WHERE $where
				       AND a.trans_id = ?|;

            my $sth = $dbh->prepare($query);
            $sth->execute( $form->{"id_$i"} );
            my ($id) = $sth->fetchrow_array;

            $amount =
              $form->round_amount( $form->{"paid_$i"} * $exchangerate, 2 );

            # add AR/AP
            $query = qq|
				INSERT INTO acc_trans 
				            (trans_id, chart_id, transdate, 
				            amount)
				     VALUES (?, ?, 
				            ?, 
				            ?)|;
            $sth = $dbh->prepare($query);
            $sth->execute( $form->{"id_$i"}, $id, $form->{datepaid},
                $amount * $ml )
              || $form->dberror( $query, __file__, __line__ );

            # add payment
            $query = qq|
				INSERT INTO acc_trans 
				            (trans_id, chart_id, transdate,
				             amount, source, memo)
				     VALUES (?, (SELECT id 
				                   FROM chart
				                  WHERE accno = ?),
				 	    ?, ?, ?, ?)|;
            $sth = $dbh->prepare($query);
            $sth->execute( $form->{"id_$i"}, $paymentaccno, $form->{datepaid},
                $form->{"paid_$i"} * $ml * -1,
                $form->{source}, $form->{memo} )
              || $form->dberror( $query, 'CP.pm', 444 );

            # add exchangerate difference if currency ne defaultcurrency
            $amount =
              $form->round_amount(
                $form->{"paid_$i"} * ( $form->{exchangerate} - 1 ), 2 );

            if ($amount) {

                # exchangerate difference
                $query = qq|
					INSERT INTO acc_trans 
					            (trans_id, chart_id, 
					            transdate, amount, cleared,
					            fx_transaction, source)
					     VALUES (?, (SELECT id 
					                   FROM chart
					                  WHERE accno = ?),
					             ?, ?, '0', '1', 
					             ?)|;
                $sth = $dbh->prepare($query);
                $sth->execute(
                    $form->{"id_$i"},   $paymentaccno, $form->{datepaid},
                    $amount * $ml * -1, $form->{source}
                ) || $form->dberror( $query, 'CP.pm', 470 );

                # gain/loss
                $amount = (
                    $form->round_amount(
                        $form->{"paid_$i"} * $exchangerate, 2
                      ) - $form->round_amount(
                        $form->{"paid_$i"} * $form->{exchangerate}, 2
                      )
                ) * $ml * -1;

                if ($amount) {

                    my $accno_id =
                      ( $amount > 0 )
                      ? $fxgain_accno_id
                      : $fxloss_accno_id;

                    $query = qq|
						INSERT INTO acc_trans 
						            (trans_id, 
						            chart_id, 
						            transdate,
						            amount, cleared, 
						            fx_transaction)
						VALUES (?, ?, ?, ?, '0', '1')|;
                    $sth = $dbh->prepare($query);
                    $sth->execute(
                        $form->{"id_$i"},  $accno_id,
                        $form->{datepaid}, $amount
                    ) || $form->dberror( $query, 'CP.pm', 506 );
                }
            }

            $form->{"paid_$i"} =
              $form->round_amount( $form->{"paid_$i"} * $exchangerate, 2 );

            $pth->execute( $form->{"id_$i"} ) || $form->dberror($pth->statement);
            ($amount) = $pth->fetchrow_array;
            $pth->finish;

            $amount += $form->{"paid_$i"};

            # update AR/AP transaction
            $query = qq|
				UPDATE $form->{arap} 
				   SET paid = ?,
				       datepaid = ?
				 WHERE id = ?|;

            $sth = $dbh->prepare($query);
            $sth->execute( $amount, $form->{datepaid}, $form->{"id_$i"} )
              || $form->dberror( $query, 'CP.pm', 530 );

            if ($amount->is_nan) {
                $dbh->rollback;
                return;
            }
            %audittrail = (
                tablename => $form->{arap},
                reference => $form->{source},
                formname  => $form->{formname},
                action    => 'posted',
                id        => $form->{"id_$i"}
            );

            $form->audittrail( $dbh, "", \%audittrail );

        }
    }

    # record a AR/AP with a payment
    if ( $form->round_amount( $paymentamount, 2 ) ) {
        $form->{invnumber} = "";
        OP::overpayment( "", $myconfig, $form, $dbh, $paymentamount, $ml, 1 );
    }

    my $rc = $dbh->commit;

    $rc;

}

sub post_payments {

    my ( $self, $myconfig, $form ) = @_;

    # connect to database, turn AutoCommit off
    my $dbh = $form->{dbh};

    my $sth;

    my ($paymentaccno) = split /--/, $form->{account};

    # if currency ne defaultcurrency update exchangerate
    if ( $form->{currency} ne $form->{defaultcurrency} ) {
        $form->{exchangerate} =
          $form->parse_amount( $myconfig, $form->{exchangerate} );

        if ( $form->{vc} eq 'customer' ) {
            $form->update_exchangerate( $dbh, $form->{currency},
                $form->{datepaid}, $form->{exchangerate}, 0 );
        }
        else {
            $form->update_exchangerate( $dbh, $form->{currency},
                $form->{datepaid}, 0, $form->{exchangerate} );
        }

    }
    else {
        $form->{exchangerate} = 1;
    }

    my $query = qq|
		SELECT (SELECT value FROM defaults 
		         WHERE setting_key='fxgain_accno_id'), 
		       (SELECT value FROM defaults
		         WHERE setting_key='fxloss_accno_id')|;

    my ( $fxgain_accno_id, $fxloss_accno_id ) = $dbh->selectrow_array($query);

    my ($buysell);

    if ( $form->{vc} eq 'customer' ) {
        $buysell = "buy";
    }
    else {
        $buysell = "sell";
    }

    my $ml;
    my $where;

    if ( $form->{ARAP} eq 'AR' ) {

        $ml    = 1;
        $where = qq| (c.link = 'AR' OR c.link LIKE 'AR:%') |;

    }
    else {

        $ml = -1;
        $where =
          qq| (c.link = 'AP' OR c.link LIKE '%:AP' OR c.link LIKE '%:AP:%') |;

    }

    # get AR/AP account
    $query = qq|SELECT c.accno 
				  FROM chart c
				  JOIN acc_trans ac ON (ac.chart_id = c.id)
				 WHERE trans_id = ?
				   AND $where|;

    my $ath = $dbh->prepare($query) || $form->dberror($query);

    # query to retrieve paid amount
    $query = qq|SELECT paid 
				  FROM $form->{arap}
				 WHERE id = ?
			FOR UPDATE|;

    my $pth = $dbh->prepare($query) || $form->dberror($query);

    my %audittrail;

    my $overpayment = 0;
    my $accno_id;

    # go through line by line
    for my $i ( 1 .. $form->{rowcount} ) {

        $ath->execute( $form->{"id_$i"} );
        ( $form->{ $form->{ARAP} } ) = $ath->fetchrow_array;
        $ath->finish;

        $form->{"paid_$i"} =
          $form->parse_amount( $myconfig, $form->{"paid_$i"} );
        $form->{"due_$i"} = $form->parse_amount( $myconfig, $form->{"due_$i"} );

        if ( $form->{"$form->{vc}_id_$i"} ne $sameid ) {

            # record a AR/AP with a payment
            if ( $overpayment > 0 && $form->{ $form->{ARAP} } ) {
                $form->{invnumber} = "";
                OP::overpayment( "", $myconfig, $form, $dbh, $overpayment, $ml,
                    1 );
            }

            $overpayment = 0;
            $form->{"$form->{vc}_id"} = $form->{"$form->{vc}_id_$i"};
            for (qw(source memo)) { $form->{$_} = $form->{"${_}_$i"} }
        }

        if ( $form->{"checked_$i"} && $form->{"paid_$i"} ) {

            $overpayment += ( $form->{"paid_$i"} - $form->{"due_$i"} );

            # get exchangerate for original
            $query = qq|
				SELECT $buysell AS fx
				  FROM exchangerate e
				  JOIN $form->{arap} a 
				       ON (a.transdate = e.transdate)
				 WHERE e.curr = ?
				       AND a.id = ?|;

            $sth = $dbh->prepare($query);
            $sth->execute( $form->{currency}, $form->{"id_$i"} )
              || $form->dberror( $query, 'CP.pm', 671 );
            my $ref = $sth->fetchrow_arrayref();
            $form->db_parse_numeric(sth => $sth, arrayref => $ref);
            my ($exchangerate) = @$ref;

            $exchangerate ||= 1;

            $query = qq|
				SELECT c.id
				  FROM chart c
				  JOIN acc_trans a ON (a.chart_id = c.id)
				 WHERE $where
				       AND a.trans_id = ?|;

            $sth = $dbh->prepare($query);
            $sth->execute( $form->{"id_$i"} );
            ($id) = $sth->fetchrow_array();

            $paid =
              ( $form->{"paid_$i"} > $form->{"due_$i"} )
              ? $form->{"due_$i"}
              : $form->{"paid_$i"};
            $amount = $form->round_amount( $paid * $exchangerate, 2 );

            # add AR/AP
            $query = qq|
				INSERT INTO acc_trans 
				            (trans_id, chart_id, transdate, 
				            amount)
				     VALUES (?, ?, ?, ?)|;

            $sth = $dbh->prepare($query);
            $sth->execute( $form->{"id_$i"}, $id, $form->{datepaid},
                $amount * $ml )
              || $form->dberror( $query, 'CP.pm', 701 );

            $query = qq|SELECT id
						  FROM chart
						 WHERE accno = ?|;

            $sth = $dbh->prepare($query);
            $sth->execute($paymentaccno);
            ($accno_id) = $sth->fetchrow_array;

            # add payment
            $query = qq|
				INSERT INTO acc_trans 
				            (trans_id, chart_id, transdate,
				            amount, source, memo)
				    VALUES (?, ?, ?, ?, ?, ?)|;

            $sth = $dbh->prepare($query);
            $sth->execute(
                $form->{"id_$i"}, $accno_id,       $form->{datepaid},
                $paid * $ml * -1, $form->{source}, $form->{memo}
            ) || $form->dberror( $query, 'CP.pm', 723 );

            # add exchangerate difference if currency ne defaultcurrency
            $amount =
              $form->round_amount(
                $paid * ( $form->{exchangerate} - 1 ) * $ml * -1, 2 );

            if ($amount) {

                # exchangerate difference
                $query = qq|
					INSERT INTO acc_trans 
					            (trans_id, chart_id, 
					            transdate,
					            amount, source)
					      VALUES (?, ?, ?, ?, ?)|;

                $sth = $dbh->prepare($query);
                $sth->execute(
                    $form->{"id_$i"}, $accno_id, $form->{datepaid},
                    $amount,          $form->{source}
                ) || $form->dberror( $query, 'CP.pm', 748 );

                # gain/loss
                $amount =
                  ( $form->round_amount( $paid * $exchangerate, 2 ) -
                      $form->round_amount( $paid * $form->{exchangerate}, 2 ) )
                  * $ml * -1;

                if ($amount) {
                    $accno_id =
                      ( $amount > 0 )
                      ? $fxgain_accno_id
                      : $fxloss_accno_id;

                    $query = qq|
						INSERT INTO acc_trans 
						            (trans_id, 
						            chart_id, 
						            transdate,
						            amount, 
						            fx_transaction)
						    VALUES (?, ?, ?, ?, '1')|;

                    $sth = $dbh->prepare($query);
                    $sth->execute(
                        $form->{"id_$i"},  $accno_id,
                        $form->{datepaid}, $amount
                    ) || $form->dberror( $query, 'CP.pm', 775 );
                }
            }

            $paid = $form->round_amount( $paid * $exchangerate, 2 );

            $pth->execute( $form->{"id_$i"} ) || $form->dberror($pth->statement);
            ($amount) = $pth->fetchrow_array;
            $pth->finish;

            $amount += $paid;

            # update AR/AP transaction
            $query = qq|
				UPDATE $form->{arap} 
				   SET paid = ?,
				       datepaid = ?
				 WHERE id = ?|;

            $sth = $dbh->prepare($query);
            $sth->execute( $amount, $form->{datepaid}, $form->{"id_$i"} )
              || $form->dberror( $query, 'CP.pm', 796 );

            if ($amount->is_nan) {
                $dbh->rollback;
                return;
            }

            %audittrail = (
                tablename => $form->{arap},
                reference => $form->{source},
                formname  => $form->{formname},
                action    => 'posted',
                id        => $form->{"id_$i"}
            );

            $form->audittrail( $dbh, "", \%audittrail );

        }

        $sameid = $form->{"$form->{vc}_id_$i"};

    }

    # record a AR/AP with a payment
    if ( $overpayment > 0 && $form->{ $form->{ARAP} } ) {
        $form->{invnumber} = "";
        OP::overpayment( "", $myconfig, $form, $dbh, $overpayment, $ml, 1 );
    }

    my $rc = $dbh->commit;

    $rc;

}

1;

